---
title: Testing
description: Test your Motia Backend System - APIs, Events
---

You built an API. You added some event handlers. Everything seems to work. But does it?

Without tests, you're guessing. With tests, you know.

Motia has `@motiadev/test` built in. It helps you:
- Test API triggers (hit endpoints, check responses)
- Test Event triggers (verify events get emitted)
- Mock contexts for unit tests

---

## Install
```bash
npm install @motiadev/test --save-dev
```

```bash
pnpm add @motiadev/test --save-dev
```

---

## Test API Triggers

Here's an API Step that creates a todo and emits an event:

```typescript title="steps/create-todo.step.ts"
export const config: ApiRouteConfig = {
  type: 'api',
  name: 'CreateTodo',
  path: '/todo',
  method: 'POST',
  emits: ['todo.created'],
  bodySchema: z.object({ description: z.string() })
}

export const handler: Handlers['CreateTodo'] = async (req, { emit }) => {
  const todo = { id: '123', description: req.body.description }
  
  await emit({ topic: 'todo.created', data: todo })
  
  return { status: 200, body: todo }
}
```

Now let's test it:

```typescript title="steps/create-todo.step.test.ts"
import { createMotiaTester } from '@motiadev/test'
import { describe, it, expect, afterAll } from 'vitest'

describe('CreateTodo', () => {
  const tester = createMotiaTester()

  afterAll(async () => {
    await tester.close()
  })

  it('should create a todo and return 200', async () => {
    const response = await tester.post('/todo', {
      body: { description: 'Buy milk' }
    })

    expect(response.status).toBe(200)
    expect(response.body).toMatchObject({
      id: expect.any(String),
      description: 'Buy milk'
    })
  })

  it('should emit todo.created event', async () => {
    const watcher = await tester.watch('todo.created')

    await tester.post('/todo', {
      body: { description: 'Buy bread' }
    })

    await tester.waitEvents()

    const events = watcher.getCapturedEvents()
    expect(events).toHaveLength(1)
    expect(events[0].data).toMatchObject({
      description: 'Buy bread'
    })
  })
})
```

**What's happening here:**
- `createMotiaTester()` → Spins up a test version of your app
- `tester.post()` → Hits your API like a real client would
- `tester.watch()` → Captures events that get emitted
- `tester.waitEvents()` → Waits for all async stuff to finish
- Then check if everything worked

---

## Test Event Triggers

Event Steps listen for events and do stuff in the background. Here's how to test them:

```typescript title="steps/process-todo.step.ts"
export const config: EventConfig = {
  type: 'event',
  name: 'ProcessTodo',
  subscribes: ['todo.created'],
  emits: ['todo.processed'],
  input: z.object({ id: z.string(), description: z.string() })
}

export const handler: Handlers['ProcessTodo'] = async (input, { emit, logger }) => {
  logger.info('Processing todo', { id: input.id })
  
  // Do some processing
  const processed = { ...input, processed: true }
  
  await emit({ topic: 'todo.processed', data: processed })
}
```

**The Test:**

```typescript title="steps/process-todo.step.test.ts"
import { createMotiaTester } from '@motiadev/test'
import { describe, it, expect, afterAll } from 'vitest'

describe('ProcessTodo', () => {
  const tester = createMotiaTester()

  afterAll(async () => {
    await tester.close()
  })

  it('should process todo when todo.created is emitted', async () => {
    const watcher = await tester.watch('todo.processed')

    // Manually emit the event that triggers the step
    await tester.emit({
      topic: 'todo.created',
      data: { id: '123', description: 'Test todo' },
      traceId: 'test-trace'
    })

    await tester.waitEvents()

    const events = watcher.getCapturedEvents()
    expect(events).toHaveLength(1)
    expect(events[0].data).toMatchObject({
      id: '123',
      description: 'Test todo',
      processed: true
    })
  })
})
```

👉 Use `tester.emit()` to manually fire events and test Event triggers without hitting APIs.

---

## Unit Test Handlers

Don't want to spin up the whole app? Test handler functions directly:

```typescript title="steps/calculate-total.step.test.ts"
import { createMockContext } from '@motiadev/test'
import { handler } from './calculate-total.step'
import { describe, it, expect } from 'vitest'

describe('CalculateTotal Handler', () => {
  it('should calculate total correctly', async () => {
    const mockContext = createMockContext()
    
    const input = { items: [{ price: 10 }, { price: 20 }] }
    
    await handler(input, mockContext)
    
    expect(mockContext.emit).toHaveBeenCalledWith({
      topic: 'total.calculated',
      data: { total: 30 }
    })
  })

  it('should log calculation', async () => {
    const mockContext = createMockContext()
    
    await handler({ items: [] }, mockContext)
    
    expect(mockContext.logger.info).toHaveBeenCalledWith(
      expect.stringContaining('Calculating total')
    )
  })
})
```

---

## Run Your Tests

**All tests:**

```bash
npm test
```

```bash
pnpm test
```

**Watch mode** (re-runs when you save files):

```bash
npm test -- --watch
```

```bash
pnpm test --watch
```

**Single test file:**

```bash
npm test -- steps/create-todo.step.test.ts
```

---

## Tester API

### `createMotiaTester()`

Starts a test version of your app.

```typescript
const tester = createMotiaTester()
```

**What you can do with it:**

| Method | What it does |
|--------|-------------|
| `post(path, options)` | Hit a POST endpoint |
| `get(path, options)` | Hit a GET endpoint |
| `emit(event)` | Fire an event manually |
| `watch(topic)` | Catch events on a topic |
| `waitEvents()` | Wait for events to finish |
| `sleep(ms)` | Pause for X milliseconds |
| `close()` | Shut down the tester |

### `createMockContext()`

Mock a context for testing handlers directly.

```typescript
const mockContext = createMockContext({
  logger: customLogger,  // optional
  emit: customEmit,      // optional
  traceId: 'custom-id'   // optional
})
```

**You get:**
- `logger` - Mock logger (Jest spy)
- `emit` - Mock emit (Jest spy)
- `traceId` - Request trace ID
- `state` - Mock state manager

---

## Tips

- ✅ **Start simple** - Test basic stuff first, then edge cases
- ✅ **Test errors** - Make sure your error handling actually works
- ✅ **Watch events** - Don't assume events fired, check them
- ✅ **Always wait** - Call `waitEvents()` or events might not finish
- ✅ **Clean up** - Always `close()` the tester when done
- ✅ **Keep it isolated** - Each test should work on its own
- ✅ **Name tests well** - Say what you're checking, not how

---
